package main;

import java.rmi.AccessException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Scanner;

import node.NodeActual;
import node.RemoteNode;
import node.RemoteNodeRMIEndpoint;

import model.Entry;
import model.HostPort;
import model.RemoteNodeData;

import registry.RemoteRegistry;

/**
 * Client side application to perform actions on chord.
 * 
 * @author the_james_marq
 * James Marquardt
 * Matt Adams
 * Justin wong
 *
 */
public class Client {

	private static String NAME_IN_REGISTRY = RemoteNodeRMIEndpoint.NAME_IN_REGISTRY;
	
	RemoteRegistry registry_stub;
	
	Scanner user_input;
	
	
	/**
	 * Main method for client application
	 * 
	 * @param args Command line arguments
	 */
	public static void main(String[] args) {
		
		if(args.length != 3) {
			System.out.println("Usage: <registry hostname> <registry port> <registry name>");
			System.exit(1);
		}
		
		new Client(args).run();
		
	}
	
	/**
	 * Constructor for Client
	 * 
	 * @param args command line args from main
	 */
	private Client(String[] args) {
		
		String registry_location = args[0];
		int registry_port = Integer.parseInt(args[1]);
		String registry_name = args[2];
		
		user_input = new Scanner(System.in);
		
		try {
			Registry registry = LocateRegistry.getRegistry(registry_location, registry_port);
			registry_stub = (RemoteRegistry) registry.lookup(registry_name);
		} catch(RemoteException e) {
			System.out.println("Unable to locate registry.");
			System.exit(1);
		} catch (NotBoundException e) {
			System.out.println("Unable to locate registry.");
			System.exit(1);
		}
	}
	
	/**
	 * Run the client interface.
	 */
	private void run() {
		
		String in;
		
		while(true) {

			System.out.print("Command (get/put/disconnect node/add node): ");
			in = user_input.nextLine();
			
			if(in.equals("get")) {
				get();
			} else if(in.equals("put")) {
				put();
			} else if(in.equals("disconnect node")) {
				disconnect();
			} else if(in.equals("add node")) {
				addNode();
			} else if (in.equals("crash node")) {
			    crashNode();
			} else if (in.equals("add test node")) {
			    addTestNode();
			} else {
				System.out.println("Invalid command");
				continue;
			}
			
		}
		
	}
	
	/**
	 * Add a node to the system.
	 */
	private void addNode() {
		System.out.print("Enter hostname: ");
		String host = user_input.nextLine();
		
		System.out.print("Enter port: ");
		int port = Integer.parseInt(user_input.nextLine());
		
		new NodeActual(registry_stub, new HostPort(host, port));
	}
	
	private void addTestNode() {
	    CreateActualNodesTest.addActualNode(this.registry_stub);
	}
	
	/**
	 * Disconnect a node from the system.
	 */
	private void disconnect() {
		
		int node_id=0;
		try {
			node_id = registry_stub.getArbitraryNode();
			System.out.println("arbitrary node id: " + node_id );
		} catch (RemoteException e1) {
			System.out.println("Error getting node.");
			return;
		}
		
		System.out.println("attempting to disconnect node " + node_id);
		HostPort loc = null;
		
		try {
			loc = registry_stub.getNodeHostPort(node_id);
			if(loc == null) {
				System.out.println("Node " + node_id + " has not been registered.");
				return;
			}
		} catch (RemoteException e) {
			System.out.println("Error calling remote method");
			return;
		}
		
        try {
        	Registry registry2 = LocateRegistry.getRegistry(loc.getHost(), loc.getPort());
            RemoteNode stub2 = (RemoteNode) registry2.lookup(NAME_IN_REGISTRY + loc.toString());
            
            RemoteNodeData stub2Data = new RemoteNodeData(stub2, stub2.getNodeID(), stub2.getNodeHostPort());
            stub2.getSuccessor().getRemoteNode().leaveNetworkNotify(stub2Data);
            
            
        } catch (NotBoundException e) {
            e.printStackTrace();
        } catch (AccessException e) {
        	System.out.println("Could not access node " + node_id);
		} catch (RemoteException e) {
			System.out.println("Could not locate node " +  node_id);
			e.printStackTrace();
		}
		
	}
	
	/**
     * Crash a node in the network.
     */
    private void crashNode() {
        
        int node_id=0;
        try {
            node_id = registry_stub.getArbitraryNode();
            System.out.println("arbitrary node id: " + node_id );
        } catch (RemoteException e1) {
            System.out.println("Error getting node.");
            return;
        }
        
        System.out.println("Crashing node with ID: " + node_id);
        HostPort loc = null;
        
        try {
            loc = registry_stub.getNodeHostPort(node_id);
            if(loc == null) {
                System.out.println("Node " + node_id + " has not been registered.");
                return;
            }
        } catch (RemoteException e) {
            System.out.println("Error calling remote method");
            return;
        }
        
        try {
            Registry registry2 = LocateRegistry.getRegistry(loc.getHost(), loc.getPort());
            RemoteNode stub2 = (RemoteNode) registry2.lookup(NAME_IN_REGISTRY + loc.toString());
            stub2.closeConnections();
            
            
        } catch (NotBoundException e) {
            e.printStackTrace();
        } catch (AccessException e) {
            System.out.println("Could not access node " + node_id);
        } catch (RemoteException e) {
            System.out.println("Could not locate node " +  node_id);
            e.printStackTrace();
        }
        
    }
	/**
	 * Calls insert method of a specified RemoteNode
	 */
	private void put() {
		//Entry ent = new Entry(42, "test");
		
		HostPort loc = null;
		
		System.out.print("Key: ");
		int key = Math.abs(user_input.nextLine().hashCode());
		
		System.out.print("Value: ");
		String value = user_input.nextLine();
		
		Entry ent = new Entry(key, value);
		
		/*
		System.out.print("Starting node ID: ");
		String in = user_input.nextLine();
		*/
		
		int node_id=0;
		try {
			node_id = registry_stub.getArbitraryNode();
			System.out.println("arbitrary node id: " + node_id );
		} catch (RemoteException e1) {
			System.out.println("Error getting node.");
			return;
		}
		/*
		try {
			node_id = Integer.parseInt(in);
		} catch (NumberFormatException e) {
			System.out.println("Node ID must be a number: ");
			return;
		}
		*/
		
		try {
			loc = registry_stub.getNodeHostPort(node_id);
			if(loc == null) {
				System.out.println("Node " + node_id + " has not been registered.");
				return;
			}
		} catch (RemoteException e) {
			System.out.println("Error calling remote method");
			return;
		}
		
        try {
        	Registry registry2 = LocateRegistry.getRegistry(loc.getHost(), loc.getPort());
            RemoteNode stub2 = (RemoteNode) registry2.lookup(NAME_IN_REGISTRY + loc.toString());

        	stub2.insertEntry(ent);
            
        } catch (NotBoundException e) {
            e.printStackTrace();
        } catch (AccessException e) {
        	System.out.println("Could not access node " + node_id);
		} catch (RemoteException e) {
			System.out.println("Could not locate node " +  node_id);
			e.printStackTrace();
		}
	}
	
	/**
	 * Gets an entry at a specified location.
	 */
	private void get() {
		
		HostPort loc = null;
		
		/*
		System.out.print("Starting node ID: ");
		String in = user_input.nextLine();
		*/
		
		int node_id=0;
		try {
			node_id = registry_stub.getArbitraryNode();
			System.out.println("arbitrary node id: " + node_id );
		} catch (RemoteException e1) {
			System.out.println("Error getting node.");
			return;
		}
		
		/*
		int node_id = 0;
		
		try {
			node_id = Integer.parseInt(in);
		} catch (NumberFormatException e) {
			System.out.println("Node ID must be a number: ");
			return;
		}
		*/
		
		try {
			loc = registry_stub.getNodeHostPort(node_id);
			if(loc == null) {
				System.out.println("Node " + node_id + " has not been registered.");
				return;
			}
		} catch (RemoteException e) {
			System.out.println("Error calling remote method");
			return;
		}
		
        try {
        	Registry registry2 = LocateRegistry.getRegistry(loc.getHost(), loc.getPort());
            RemoteNode stub2 = (RemoteNode) registry2.lookup(NAME_IN_REGISTRY + loc.toString());
            
            System.out.print("Key: ");
            String in = user_input.nextLine();
            int key = Math.abs(in.hashCode());

            Entry ret = stub2.getEntry(key);
            if(ret == null) {
            	System.out.println("No value stored at key " + in);
            } else {
            	System.out.println("Value at key " + in + ": " + ret.getValue());
            }
        } catch (NotBoundException e) {
        	e.printStackTrace();
        } catch (AccessException e) {
        	System.out.println("Could not access node " + node_id);
        } catch (RemoteException e) {
        	System.out.println("Could not locate node " +  node_id);
        	e.printStackTrace();
        }
	}

}
